import { ExecException } from "child_process";
import * as fs from "fs";
import path from "path";
import { Config } from "./Config/Config";
import { OSUtils } from "./Utils/OSUtils";

export class DiffBuilder{

    /**
     * 单例模式
     */
    private static s_Builder:DiffBuilder;

    /**
     * 单例模式
     */
    public static get inst():DiffBuilder{
        if(DiffBuilder.s_Builder == null){
            DiffBuilder.s_Builder = new DiffBuilder();
        }
        return DiffBuilder.s_Builder;
    }

    /**
     * 最新版本所有文件的MD5值
     */
    private m_mapMD5:Map<string,string> = new Map();
    /**
     * 当前在处理的版本索引
     */
    private m_verCount:number = 0;
    /**
     * 当前在处理的版本号
     */
    private m_curVer:number = 0;
    /**
     * 所有版本号
     */
    private m_arrAllVer:number[] = [];
    /**
     * 当前最新版本号
     */
    private m_maxVer:number = 0;
    /**
     * 最新版本号的所有文件
     */
    private m_paMaxVer:string[] = [];
    /**
     * 当前在处理的版本更新临时目录(1-2)
     */
    private m_curUpdatePath:string = "";
    /**
     * 保存已经生成过增量更新文件的MD5
     */
	private m_oldFileMD5Cache:Map<string,string> = new Map();
    /**
     * 所有临时目录
     */
	private m_allTmpPaths:string[] = [];
    /**
     * 当前在处理的版本文件索引
     */
    private m_curFileIdx:number = 0;

    /**
     * 开始构建增量文件
     */
    public StartBuild():void{
        console.log("开始构建增量更新文件!");
        this.BuildUpdate();
    }

    private BuildUpdate():void{
        let arrPaths:string[] = fs.readdirSync(Config.DataPath);
        let me:DiffBuilder = this;
        arrPaths.forEach(function(ele:string,index:number){
            var info = fs.statSync(path.join(Config.DataPath,ele));
            if(info.isDirectory()){
                console.log("发现版本: " + ele)
                let num = parseInt(ele);
                if(!isNaN(num)){
                    me.m_arrAllVer.push(parseInt(ele));
                }
            }else{
                console.log("忽略文件: " + ele)
            }
        });
        
        this.m_arrAllVer = this.m_arrAllVer.sort((a,b)=>a-b);
        this.m_maxVer = this.m_arrAllVer[this.m_arrAllVer.length - 1];
        this.m_paMaxVer = fs.readdirSync(path.join(Config.DataPath,this.m_maxVer.toString()));

        console.log("当前最新版本:" + this.m_maxVer);
        this.GenMD5(path.join(Config.DataPath,this.m_maxVer.toString()),this.ProcessUpdate.bind(this));
    }

    private ProcessUpdate():void{
        this.m_verCount = 0;
        this.ProcessVer(this.m_arrAllVer[this.m_verCount]);
    }

    private ProcessVer(ver:number):void{
        if(ver == this.m_maxVer){
            return;
        }
		this.m_curVer = ver;
        this.m_curUpdatePath = path.join(Config.DataPath , ver + "-" + this.m_maxVer);
        
        if(fs.existsSync(this.m_curUpdatePath)){
            OSUtils.DelDir(this.m_curUpdatePath)
        }
        
        fs.mkdirSync(this.m_curUpdatePath);
        this.m_allTmpPaths.push(this.m_curUpdatePath);
        this.m_curFileIdx = 0;
        this.ProcessVerNext();
    }

    private ProcessVerNext():void{
        let ele:string = this.m_paMaxVer[this.m_curFileIdx];
        let me:DiffBuilder = this;
        let oldFile = path.join(Config.DataPath , this.m_curVer.toString() , ele);
        let newFile = path.join(Config.DataPath , this.m_maxVer.toString() , ele);
        if(fs.existsSync(oldFile)){
            OSUtils.GetFileMD5(oldFile,function(strMD5:string){
                let patchFile = path.join(me.m_curUpdatePath , ele + ".patch");

                if(me.m_oldFileMD5Cache.has(strMD5)){
                    let prePatchFile = me.m_oldFileMD5Cache.get(strMD5);
                    OSUtils.CopyFile(prePatchFile,patchFile);
                    me.CheckProcessVerComplete(ele);
                }else{
                    if(strMD5 != me.m_mapMD5.get(ele)){
                        console.log("处理增量文件：" + ele)
                        OSUtils.execCmd(Config.BSdiffPath + " " + oldFile + " " + newFile + " " + patchFile,{},function(err:ExecException | null,stdout:string,stderr:string){
                            if(err){
                                console.log("处理失败:" + ele);
                            }else{
                                me.m_oldFileMD5Cache.set(strMD5,patchFile);
                                me.CheckProcessVerComplete(ele);
                            }
                        });
                    }else{
                        me.CheckProcessVerComplete(ele);
                    }
                }
            });
        }else{
            console.log("这是个新文件，直接移动文件：" + ele);
            OSUtils.CopyFile(newFile,path.join(this.m_curUpdatePath,ele));
            this.CheckProcessVerComplete(ele);
        }
    }

    private CheckProcessVerComplete(ele:string):void{
        this.m_curFileIdx ++;
        console.log("处理完成:" + ele);
        if(this.m_curFileIdx == this.m_paMaxVer.length){
            console.log("版本处理完成:" + this.m_curVer);
            console.log("开始压缩文件!");
            
            OSUtils.CopyFile(path.join(this.m_curUpdatePath , "../MD5.json"),path.join(this.m_curUpdatePath , "MD5.json"));
            OSUtils.Zip(this.m_curUpdatePath,path.join(Config.DataPath , this.m_curVer + ".zip"));
            OSUtils.DelFile(path.join(this.m_curUpdatePath , "MD5.json"));

            this.m_verCount ++;
            if(this.m_verCount >= this.m_arrAllVer.length - 1){
                console.log("处理所有版本完成,压缩最新版本!");
                
                let maxVerPath = path.join(Config.DataPath , this.m_maxVer.toString());
                let maxZipFilePath = path.join(Config.DataPath , this.m_maxVer + ".zip");

                OSUtils.CopyFile(path.join(this.m_curUpdatePath , "../MD5.json"),path.join(maxVerPath , "MD5.json"));
                OSUtils.Zip(maxVerPath,maxZipFilePath);
                OSUtils.DelFile(path.join(maxVerPath , "MD5.json"));

                for (var i = 0; i < this.m_allTmpPaths.length; i++) {
                    OSUtils.DelDir(this.m_allTmpPaths[i]);
                }

            }else{
                this.ProcessVer(this.m_arrAllVer[this.m_verCount]);
            }
        }else{
            this.ProcessVerNext();
        }
    }

    private GenMD5(path:string,callBack:Function):void{
        let allFile:string[] = fs.readdirSync(path);
        let allMD5:Object[] = [];
        let md5Count:number = 0;
        let me:DiffBuilder = this;
        for (var i:number = 0; i < allFile.length; i++) {
            let fileName:string = allFile[i];
            OSUtils.GetFileMD5(path + "/" + fileName,function(strMD5:string):void{
                allMD5.push({
                    file:fileName,
                    MD5:strMD5
                });
                me.m_mapMD5.set(fileName,strMD5);
                md5Count ++;
                if(md5Count >= allFile.length){
                    OSUtils.SaveStringToFile(path + "/../MD5.json",JSON.stringify(allMD5));
                    callBack();
                }
            });
        }
    }

}